package com.anjie.powerproject.service.impl;

import com.anjie.powerproject.common.Constants;
import com.anjie.powerproject.common.FillStrategyEnum;
import com.anjie.powerproject.entity.*;
import com.anjie.powerproject.repository.*;
import com.anjie.powerproject.strategy.utils.DateUtils;
import com.anjie.powerproject.strategy.utils.DecimalFormatUtils;
import com.anjie.powerproject.util.CsvData;
import com.anjie.powerproject.util.CsvUtil;
import com.fasterxml.jackson.datatype.jsr310.DecimalUtils;
import com.github.javafaker.Faker;
import lombok.Data;
import org.ini4j.Ini;
import org.ini4j.Profile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.ParseException;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class DataLoader {

    @Autowired
    GraphInfoRepository graphInfoRepository;

    @Autowired
    StationInfoRepository stationInfoRepository;

    @Autowired
    WeatherRepository weatherRepository;

    @Autowired
    DataDayRepository dataDayRepository;

    @Autowired
    MAverageRepository mAverageRepository;

    @Data
    public class DataRow {
        String sdate;

        WeatherData weather;

        BigDecimal data;
    }

    @Data
    public class DataSet {
        List<String> header;

        List<List<String>> data;
    }

    private GraphInfo getGraphInfo(String date) {
        GraphInfo graphInfo = graphInfoRepository.getGraphByDateStr(date);
        if (graphInfo == null) {
            throw new RuntimeException(String.format("未获取到图谱信息 date:%s", date));
        }
        return graphInfo;
    }

    /**
     * 获取天级气象信息
     */
    public List<WeatherData> getDayWeather(Date startTime, Date endTime) {
        // 首先获取citycode
        StationInfo stationInfo = stationInfoRepository.findStationInfoLimitFirst();
        String cityCode = stationInfo.getCityCode();

        String start = DateUtils.ymdFormat.format(startTime);
        String end = DateUtils.ymdFormat.format(endTime);

        Calendar nowCalendar = DateUtils.dateToCalender(new Date());
        nowCalendar.set(Calendar.HOUR_OF_DAY, 0);
        nowCalendar.set(Calendar.MINUTE, 0);
        nowCalendar.set(Calendar.SECOND, 0);
        nowCalendar.set(Calendar.MILLISECOND, 0);
        Date now = nowCalendar.getTime();

        // 如果全部是历史数据
        if (endTime.getTime() < now.getTime()) {
            return weatherRepository.getHistoryDayWeatherListByDate(cityCode, start, end);
        } else {
            if (startTime.getTime() >= now.getTime()) {
                return weatherRepository.getDayWeatherListByDate(cityCode, start, end);
            } else {
                // 一半一半
                nowCalendar.add(Calendar.DATE, -1);
                String nowBefore = DateUtils.ymdFormat.format(nowCalendar.getTime());
                List<WeatherData> weatherList = weatherRepository.getHistoryDayWeatherListByDate(cityCode, start, nowBefore);

                String nowAfter = DateUtils.ymdFormat.format(now);
                List<WeatherData> futureList = weatherRepository.getDayWeatherListByDate(cityCode, nowAfter, end);
                weatherList.addAll(futureList);

                return weatherList;
            }
        }
    }

    public List<DataRow> loadOriginData(Date startTime, Date endTime, String devCode, FillStrategyEnum fillStrategy, Double val) {
        List<DataRow> dataRowList = new ArrayList<>();

        String start = DateUtils.ymdFormat.format(startTime);
        String end = DateUtils.ymdFormat.format(endTime);

        List<DataDay> dataDayList = dataDayRepository.getDataDayList(devCode, start, end);
        fillDataDayList(startTime, endTime, devCode, dataDayList, fillStrategy, val);

        for (DataDay dataDay : dataDayList) {
            DataRow dataRow = new DataRow();
            dataRow.setSdate(DateUtils.ymdFormat.format(dataDay.getSdate()));
            dataRow.setData(dataDay.getData());
            dataRowList.add(dataRow);
        }

        // 读取天气数据
        List<WeatherData> weatherDataList = getDayWeather(startTime, endTime);
        Map<String, WeatherData> weatherDataMap = weatherDataList.stream().collect(Collectors.toMap(WeatherData::getSdate, e -> e));

        // 合并数据
        for (DataRow dataRow : dataRowList) {
            WeatherData weatherData = weatherDataMap.get(dataRow.getSdate());
            dataRow.setWeather(weatherData);
        }

        return dataRowList;
    }

    private void fillDataDayList(Date startTime, Date endTime, String devCode, List<DataDay> dataDayList, FillStrategyEnum fillStrategy, Double val) {
        // 如果填充策略是UNDO，则不做填充，其余的做相应的填充
        switch (fillStrategy) {
            case FILL_ZERO:
                fillDataDayWithFixValue(startTime, endTime, devCode, dataDayList, 0.0);
                break;
            case FILL_POSITIVE_ONE:
                fillDataDayWithFixValue(startTime, endTime, devCode, dataDayList, 1.0);
                break;
            case FILL_NEGATIVE_ONE:
                fillDataDayWithFixValue(startTime, endTime, devCode, dataDayList, -1.0);
                break;
            case FILL_OTHER_VALUE:
                if (val == null) {
                    throw new RuntimeException("选择填充指定值策略时必须要提供要填充的值!");
                }
                fillDataDayWithFixValue(startTime, endTime, devCode, dataDayList, val);
                break;
            case UNDO:
            default:
                break;
        }

    }

    private void fillDataDayWithFixValue(Date startTime, Date endTime, String devCode, List<DataDay> dataDayList, Double val) {
        Calendar startCalender = DateUtils.dateToCalender(startTime);
        Calendar endCalender = DateUtils.dateToCalender(endTime);

        Map<String, DataDay> dataDayMap = dataDayList.stream().collect(Collectors.toMap(e -> DateUtils.ymdFormat.format(e.getSdate()), e -> e));
        for (Calendar calendar = startCalender; calendar.compareTo(endCalender) <= 0; calendar.add(Calendar.DATE, 1)) {
            String time = DateUtils.ymdFormat.format(calendar.getTime());
            if (dataDayMap.get(time) == null) {
                DataDay dataDay = new DataDay();
                dataDay.setData(BigDecimal.valueOf(val));
                dataDay.setSdate(calendar.getTime());
                dataDay.setDevCode(devCode);

                dataDayList.add(dataDay);
            }
        }

        // 最后对DataDayList进行排序
        dataDayList.sort(Comparator.comparingLong(e -> e.getSdate().getTime()));
    }

    public List<Integer> getHoliday(List<String> dateList, String[] bases, String filename) throws IOException, ParseException {
        String base = "";
        for (String b : bases) {
            File file = new File(b);
            if (file.exists()) {
                base = b;
                break;
            }
        }

        CsvData csvData = CsvUtil.loadCsv(base + Constants.PYTHON_DIR + "common/" + filename);
//        CsvData csvData = CsvUtil.loadCsv(base + filename);
        List<String> header = csvData.getHeader();
        int dateIdx = header.indexOf("SDATE");
        int holidayIdx = header.indexOf("IsHoliday");

        List<List<String>> data = csvData.getData();
        Map<String, Integer> holidayInfoMap = data.stream().collect(Collectors.toMap(e -> e.get(dateIdx), (e -> Integer.parseInt(e.get(holidayIdx)))));

        List<Integer> holidayInfos = new ArrayList<>();
        for (String date : dateList) {
            Integer isHoliday = holidayInfoMap.get(String.join("", date.split("-")));
            holidayInfos.add(isHoliday);
        }

        return holidayInfos;
    }

    public List<Integer> getMonth(List<String> dateList) throws ParseException {
        List<Integer> months = new ArrayList<>();
        for (String dateStr : dateList) {
            Calendar date = DateUtils.dateToCalender(DateUtils.ymdFormat.parse(dateStr));
            int month = date.get(Calendar.MONTH);
            months.add(month);
        }

        return months;
    }

    public List<Integer> getDay(List<String> dateList) throws ParseException {
        List<Integer> days = new ArrayList<>();
        for (String dateStr : dateList) {
            Calendar date = DateUtils.dateToCalender(DateUtils.ymdFormat.parse(dateStr));
            int day = date.get(Calendar.DATE);
            days.add(day);
        }

        return days;
    }

    public List<Integer> getSeason(List<String> dateList) throws ParseException {
        List<GraphInfo> graphInfoList = graphInfoRepository.findAll();
        List<Integer> seasons = new ArrayList<>();
        for (String dateStr : dateList) {
            Date date = DateUtils.dateToCalender(DateUtils.ymdFormat.parse(dateStr)).getTime();
            Integer type = getGraphType(date, graphInfoList);

            if (type == null) {
                seasons.add(2);
            } else {
                seasons.add(type);
            }
        }

        return seasons;
    }

    public Integer getGraphType(Date date, List<GraphInfo> graphInfoList) throws ParseException {
        for (GraphInfo graphInfo : graphInfoList) {
            Date startTime = DateUtils.ymdFormat.parse(1900 + date.getYear() + "-" + graphInfo.getGraphStartTime());
            startTime = DateUtils.plusOrMinusDay(startTime, -1).getTime();
            Date endTime = DateUtils.ymdFormat.parse(1900 + date.getYear() + "-" + graphInfo.getGraphEndTime());
            endTime = DateUtils.plusOrMinusDay(endTime, 1).getTime();

            if (endTime.before(startTime)) {
                if (date.after(startTime)) {
                    endTime.setYear(endTime.getYear() + 1);
                }
                else if(date.before(endTime)) {
                    startTime.setYear(startTime.getYear() - 1);
                }

                if (date.after(startTime) && date.before(endTime)) {
                    return graphInfo.getGraph_type();
                }
            } else {
                if (date.after(startTime) && date.before(endTime)){
                    return graphInfo.getGraph_type();
                }
            }
        }

        return null;
    }

    public List<Integer> getWeekYear(List<String> dateList) throws ParseException {
        List<Integer> weekYears = new ArrayList<>();
        for (String dateStr : dateList) {
            Calendar date = DateUtils.dateToCalender(DateUtils.ymdFormat.parse(dateStr));
            int weekYear = date.getWeekYear();
            weekYears.add(weekYear);
        }

        return weekYears;
    }

    public List<Integer> getWeekDay(List<String> dateList) throws ParseException {
        List<Integer> weekDays = new ArrayList<>();
        for (String dateStr : dateList) {
            Calendar date = DateUtils.dateToCalender(DateUtils.ymdFormat.parse(dateStr));
            int weekday = date.get(Calendar.DAY_OF_WEEK);

            if (weekday > 1) {
                weekDays.add(weekday - 1);
            } else {
                weekDays.add(7);
            }
        }

        return weekDays;
    }

    public List<Double> getDayTMean(List<Double> dayTMaxList, List<Double> dayTMinList) {
        List<Double> dayTMeanList = new ArrayList<>();

        for (int i = 0; i < dayTMaxList.size(); i++) {
            Double dayTMax = dayTMaxList.get(i);
            Double dayTMin = dayTMinList.get(i);

            if (dayTMax != null && dayTMin != null) {
                dayTMeanList.add((dayTMax + dayTMin) / 2);
            } else {
                dayTMeanList.add(null);
            }
        }

        return dayTMeanList;
    }

    public List<Integer> getHolidayPlusSeason(List<String> dateList, String[] bases, String filename) throws IOException, ParseException {
        List<Integer> holiday = getHoliday(dateList, bases, filename);
        List<Integer> season = getSeason(dateList);

        List<Integer> holidayPlusSeason = new ArrayList<>();
        for (int i = 0; i < holiday.size(); i++) {
            holidayPlusSeason.add(holiday.get(i) + season.get(i));
        }

        return holidayPlusSeason;
    }

    public List<String> readIniFile(String iniPath, String defaultPath) throws IOException {
        // 加载ini文件
        try {
            Ini ini = new Ini();
            File iniFile = new File(iniPath);
            if (!iniFile.exists()) {
                iniFile = new File(defaultPath);
            }
//            File file=new File("D:\\Anjie\\Multimode-Project_anjie\\src\\main\\resources\\static\\python\\common\\model.ini");
//            ini.load(file);
            ini.load(iniFile);
            Profile.Section features = ini.get("feature");
            String dayFeatures = features.get("day_feature");

            return Arrays.stream(dayFeatures.split(",")).collect(Collectors.toList());
        } catch (FileNotFoundException e) {
            if (iniPath.contains("electric")) {
                return Constants.ELECTRIC_DEFAULT_COLUMNS;
            }
            if (iniPath.contains("energy")) {
                return Constants.ENERGY_DEFAULT_COLUMNS;
            }

            throw new RuntimeException("未找到ini配置文件");
        }
    }

    /**
     * 以月为单位的温度均值
     * @return
     */
    public List<Double> getMAverageMax(List<String> dateList, Map<Integer, List<MAverage>> mAverageSplitByMonth) throws ParseException {
        Map<Integer, Double> mAverageMaxMap = new HashMap<>();
        for (int i = 1; i <= 12; i++) {
            double sum = 0.0;
            List<MAverage> mAverages = mAverageSplitByMonth.get(i);
            if (mAverages != null) {
                for (MAverage mAverage : mAverages) {
                    sum += mAverage.getMAverageMax();
                }
            }

            mAverageMaxMap.put(i, DecimalFormatUtils.round(sum / mAverages.size(), "0.01"));
        }

        List<Double> mAverageMaxList = new ArrayList<>();
        for (String date : dateList) {
            Date m = DateUtils.parseStringToDate(DateUtils.ymdFormat, date);
            Calendar mCalener = DateUtils.dateToCalender(m);
            int month = mCalener.get(Calendar.MONTH) + 1;
            mAverageMaxList.add(mAverageMaxMap.get(month));
        }

        return mAverageMaxList;
    }

    public List<Double> getMAverageMin(List<String> dateList, Map<Integer, List<MAverage>> mAverageSplitByMonth) throws ParseException {
        Map<Integer, Double> mAverageMinMap = new HashMap<>();
        for (int i = 1; i <= 12; i++) {
            double sum = 0.0;
            List<MAverage> mAverages = mAverageSplitByMonth.get(i);
            if (mAverages != null) {
                for (MAverage mAverage : mAverages) {
                    sum += mAverage.getMAverageMin();
                }
            }

            mAverageMinMap.put(i, DecimalFormatUtils.round(sum / mAverages.size(), "0.01"));
        }

        List<Double> mAverageMinList = new ArrayList<>();
        for (String date : dateList) {
            Date m = DateUtils.parseStringToDate(DateUtils.ymdFormat, date);
            Calendar mCalener = DateUtils.dateToCalender(m);
            int month = mCalener.get(Calendar.MONTH) + 1;
            mAverageMinList.add(mAverageMinMap.get(month));
        }

        return mAverageMinList;
    }

    private String notNull(Object obj) {
        if (obj == null) {
            return null;
        }
        return obj.toString();
    }

    private void addColumn(String[] bases, List<String> dateList, List<DataRow> dataRowList, List<String> headers, List<List<String>> data, String feature) throws IOException, ParseException {
        switch (feature) {
            case "day_wind":
                headers.add("day_wind");
                for (int i = 0; i < data.size(); i++) {
                    data.get(i).add(dataRowList.get(i).getWeather() != null ? notNull(dataRowList.get(i).getWeather().getDayWind()) : null);
                }
                break;
            case "day_hum_max":
                headers.add("day_hum_max");
                for (int i = 0; i < data.size(); i++) {
                    data.get(i).add(dataRowList.get(i).getWeather() != null ? notNull(dataRowList.get(i).getWeather().getDayHumMax()) : null);
                }
                break;
            case "day_hum_min":
                headers.add("day_hum_min");
                for (int i = 0; i < data.size(); i++) {
                    data.get(i).add(dataRowList.get(i).getWeather() != null ? notNull(dataRowList.get(i).getWeather().getDayHumMin()) : null);
                }
                break;
            case "day_weather":
                headers.add("day_weather");
                for (int i = 0; i < data.size(); i++) {
                    data.get(i).add(dataRowList.get(i).getWeather() != null ? notNull(dataRowList.get(i).getWeather().getDayWeather()) : null);
                }
                break;
            case "day_t_max":
                headers.add("day_t_max");
                for (int i = 0; i < data.size(); i++) {
                    data.get(i).add(dataRowList.get(i).getWeather() != null ? notNull(dataRowList.get(i).getWeather().getDayTMax()) : null);
                }
                break;
            case "day_t_min":
                headers.add("day_t_min");
                for (int i = 0; i < data.size(); i++) {
                    data.get(i).add(dataRowList.get(i).getWeather() != null ? notNull(dataRowList.get(i).getWeather().getDayTMin()) : null);
                }
                break;
            case "day_t_mean":
                List<Double> dayTMaxList = dataRowList.stream().map(e -> e.getWeather() != null ? e.getWeather().getDayTMax() : null).collect(Collectors.toList());
                List<Double> dayTMinList = dataRowList.stream().map(e -> e.getWeather() != null ? e.getWeather().getDayTMin() : null).collect(Collectors.toList());

                List<Double> dayTMean = getDayTMean(dayTMaxList, dayTMinList);
                headers.add("day_t_mean");
                for (int i = 0; i < data.size(); i++) {
                    data.get(i).add(Objects.toString(dayTMean.get(i)));
                }
                break;
            case "holiday":
                List<Integer> holiday = getHoliday(dateList, bases, "holiday.csv");
                headers.add("holiday");
                for (int i = 0; i < data.size(); i++) {
                    data.get(i).add(Objects.toString(holiday.get(i)));
                }
                break;
            case "season":
                List<Integer> season = getSeason(dateList);
                headers.add("season");
                for (int i = 0; i < data.size(); i++) {
                    data.get(i).add(Objects.toString(season.get(i)));
                }
                break;
            case "holiday_plus_season":
                List<Integer> holidayPlusSeason = getHolidayPlusSeason(dateList, bases, "holiday.csv");
                headers.add("holiday_plus_season");
                for (int i = 0; i < data.size(); i++) {
                    data.get(i).add(Objects.toString(holidayPlusSeason.get(i)));
                }
                break;
            case "weekdays":
                List<Integer> weekDay = getWeekDay(dateList);
                headers.add("weekdays");
                for (int i = 0; i < data.size(); i++) {
                    data.get(i).add(Objects.toString(weekDay.get(i)));
                }
                break;
            case "m_average_day_t_max":
                List<MAverage> mAverageList1 = mAverageRepository.getMAverage();
                Map<Integer, List<MAverage>> mAverageSplitByMonth1 = mAverageList1.stream().collect(Collectors.groupingBy(MAverage::getMonth));

                List<Double> mAverageMax = getMAverageMax(dateList, mAverageSplitByMonth1);
                headers.add("m_average_day_t_max");
                for (int i = 0; i < data.size(); i++) {
                    data.get(i).add(Objects.toString(mAverageMax.get(i)));
                }
                break;
            case "m_average_day_t_min":
                List<MAverage> mAverageList2 = mAverageRepository.getMAverage();
                Map<Integer, List<MAverage>> mAverageSplitByMonth2 = mAverageList2.stream().collect(Collectors.groupingBy(MAverage::getMonth));

                List<Double> mAverageMin = getMAverageMin(dateList, mAverageSplitByMonth2);
                headers.add("m_average_day_t_min");
                for (int i = 0; i < data.size(); i++) {
                    data.get(i).add(Objects.toString(mAverageMin.get(i)));
                }
                break;
            case "month":
                List<Integer> monthList = getMonth(dateList);
                headers.add("month");
                for (int i = 0; i < data.size(); i++) {
                    data.get(i).add(Objects.toString(monthList.get(i)));
                }
                break;
            case "day":
                List<Integer> dayList = getDay(dateList);
                headers.add("day");
                for (int i = 0; i < data.size(); i++) {
                    data.get(i).add(Objects.toString(dayList.get(i)));
                }
                break;
            case "weekyear":
                List<Integer> weekYear = getWeekYear(dateList);
                headers.add("weekyear");
                for (int i = 0; i < data.size(); i++) {
                    data.get(i).add(Objects.toString(weekYear.get(i)));
                }
                break;
            default:
                break;
        }
    }

    public DataSet buildData(String[] bases, Date startTime, Date endTime, String devCode, FillStrategyEnum fillStrategy, Double val) throws IOException, ParseException {
        // 首先查找model目录下是否存在ini文件，如果不存在则取默认地址的ini文件
        String base = "";
        for (String b : bases) {
            File file = new File(b);
            if (file.exists()) {
                base = b;
                break;
            }
        }

        List<String> features = readIniFile(String.format(base + Constants.PYTHON_DIR + Constants.MODEL_INI_FILE, devCode, devCode), base + Constants.PYTHON_DIR + Constants.DEFAULT_INI_FILE);
        List<DataRow> dataRowList = loadOriginData(startTime, endTime, devCode, fillStrategy, val);

        DataSet dataSet = new DataSet();
        List<String> headers = new ArrayList<>(Arrays.asList("sdate", "data"));
        dataSet.setHeader(headers);

        List<List<String>> data = new ArrayList<>();
        for(DataRow dataRow : dataRowList) {
            List<String> row = new ArrayList<>();
            row.add(dataRow.getSdate());
            row.add(dataRow.getData().toString());
            data.add(row);
        }
        dataSet.setData(data);
        List<String> dateList = dataRowList.stream().map(DataRow::getSdate).collect(Collectors.toList());
        for (String feature : features) {
            addColumn(bases, dateList, dataRowList, headers, data, feature);
        }

        int count = DateUtils.count(startTime, endTime);
        // 过滤值为空的数据
        data = data.stream().filter(this::notNull).collect(Collectors.toList());
        if (data.size() / (count + 0.0) < 0.9) {
            throw new RuntimeException("缺失原始数据");
        }
        dataSet.setData(data);
        return dataSet;
    }

    private boolean notNull(List<String> row) {
        for (String col : row) {
            if (col == null) {
                return false;
            }
        }
        return true;
    }

    private String upper(String devCode) {
        if (devCode != null) {
            return devCode.toUpperCase(Locale.ROOT);
        }
        return devCode;
    }
}
